
#include <math.h>
#include "../iface/debug.h"
#include "../iface/testerror.h"
#include "../iface/APRInterface.h"
#include "../utils/Hash.h"
#include "../net/BrokerMessage.h"
#include "Actor.h"
#include "ActorsDB.h"

#ifndef PRId64
#define PRId64 "ld"
#endif

/** The actor type names also double as the table names in the actor
 * database. */
const char *Actor::actortypename[] = { "unset",
                                       "actors",
                                       "users",
                                       "computes",
                                       "workinstances",
                                       "datastores", NULL };

/** Constructs a lazy-bound actor connected to the actor database.
 *
 * @param inewadb The actors database to link with this actor. */
Actor::Actor(ActorsDB *inewadb)
{
   initNull();
   newadb = inewadb;
   if (!inewadb)
      errprint("actor database is NULL");
   sqlite_actor_basic = NULL;
   sqlite_actor_extended = NULL;
   thistype = ACTOR_TYPE_UNSET;
}

/** Destroys the lazy-bound actor. */
Actor::~Actor()
{
   if (sqlite_actor_extended) delete sqlite_actor_extended;
   sqlite_actor_extended = NULL;
   if (sqlite_actor_basic) delete sqlite_actor_basic;
   sqlite_actor_basic = NULL;
}

/** Sets the name of the actor. This is the primary key in the
 * database, so if reading from the database is the intention, the
 * various get functions can now be called freely. If writing is
 * required, after calling any set functions, commit() can be used to
 * write the new data to the database.
 * 
 * @param iname The actor name.
 *
 * @see commit()
 */
void Actor::setName(const char *iname)
{
   FATAL_IF_BLANK((void)0, iname, "actor name");
   if (sqlite_actor_basic) delete sqlite_actor_basic;
   if (sqlite_actor_extended) delete sqlite_actor_extended;
   sqlite_actor_basic = new SQLiteTransactor(newadb, "actors", "name", iname);
   sqlite_actor_extended = NULL;
}

/** Sets the actor name to a newly generated UUID.
 * 
 * @see setName()
 *
 * @see commit()
 */
void Actor::generateName()
{
   setName(Hash::makeHexadecimalID(32));
}

/** Get actor name. This is slightly redundant as we must have added
 * the name into the actor class already.
 * 
 * @return A constant pointer to the actor name inside this. */
const char *Actor::getName()
{
   if (sqlite_actor_basic) return sqlite_actor_basic->getField("name");
   return NULL;
}

/** Set actor type. This is needed to be able to write extended actor
 * information to the appropriate extended tables.
 * 
 * @param itype The type ID of the actor. */
void Actor::setType(actortype itype)
{
   if (thistype != ACTOR_TYPE_UNSET)
   {
      errprint("actor type has already been set!");
      return;
   }
   const char *thisname = getName();
   if (thisname == NULL)
   {
      errprint("actor name has not been set!");
      return;
   }
   thistype = itype;
   const char *classdesc = NULL;
   switch (thistype)
   {
      case ACTOR_TYPE_UNSET: break;
      case ACTOR_TYPE_USER: classdesc = "users"; break;
      case ACTOR_TYPE_COMPUTE: classdesc = "computes"; break;
      case ACTOR_TYPE_WORKINSTANCE: classdesc = "workinstances"; break;
      case ACTOR_TYPE_DATASTORE: classdesc = "datastores"; break;
   }
   switch (thistype)
   {
      case ACTOR_TYPE_UNSET:
         sqlite_actor_extended = NULL;
         break;
      default:
         sqlite_actor_basic->setField("class", classdesc);
         sqlite_actor_extended = new SQLiteTransactor(newadb, actortypename[(int)(log2(thistype))], "name", thisname);
         break;
   }
}

/** Get actor type.
 * 
 * @return The actor type ID. */
const Actor::actortype Actor::getType()
{
   if (thistype != ACTOR_TYPE_UNSET)
      return thistype;
   if (sqlite_actor_basic == NULL)
   {
      errprint("no basic actor record loaded");
      return ACTOR_TYPE_UNSET;
   }
   const char *typestring = sqlite_actor_basic->getField("class");
   if (strcmp(typestring, "users") == 0) thistype = ACTOR_TYPE_USER;
   if (strcmp(typestring, "computes") == 0) thistype = ACTOR_TYPE_COMPUTE;
   if (strcmp(typestring, "workinstances") == 0) thistype = ACTOR_TYPE_WORKINSTANCE;
   if (strcmp(typestring, "datastores") == 0) thistype = ACTOR_TYPE_DATASTORE;
   switch (thistype)
   {
      case ACTOR_TYPE_UNSET:
         sqlite_actor_extended = NULL;
         break;
      default:
         sqlite_actor_extended = new SQLiteTransactor(newadb, actortypename[(int)(log2(thistype))], "name", getName());
         break;
   }
   return thistype;
}

/** Get the remaining active time on the actor.
 * 
 * @return The number of seconds left to live.
 * If zero, the actor is dead. If -1, the actor
 * will never die. */
int64_t Actor::getRemainingLife()
{
   int64_t v_from = (int64_t)getValidFromAsDate();
   int64_t v_to = (int64_t)getExpiresOnAsDate();
   int64_t v_now = apr_time_now();
   if ((v_now - v_to) < 0) return 0;
   if ((v_from - v_now) < 0) return 0;
   return (v_now - v_from);
}

/** Get actor type name.
 * 
 * @return The actor type name as a constant string. */
const char *Actor::getTypeName()
{
   if (thistype == ACTOR_TYPE_UNSET) getType();
   return actortypename[(int)(log2(thistype))];
}

/** Use the actor type name string to set the actor type ID.
 * 
 * @param istrtype One of unset, actors, users, computes,
 * workinstances, datastores and keydependencies. */
void Actor::setTypeName(const char *istrtype)
{
   actortype iactor_type = ACTOR_TYPE_UNSET;
   if (strcmp(istrtype, "unset") == 0) setType(ACTOR_TYPE_UNSET);
   if (strcmp(istrtype, "actors") == 0) setType(ACTOR_TYPE_GENERIC);
   if (strcmp(istrtype, "users") == 0) setType(ACTOR_TYPE_USER);
   if (strcmp(istrtype, "computes") == 0) setType(ACTOR_TYPE_COMPUTE);
   if (strcmp(istrtype, "workinstances") == 0) setType(ACTOR_TYPE_WORKINSTANCE);
   if (strcmp(istrtype, "datastores") == 0) setType(ACTOR_TYPE_DATASTORE);
}

SETGET_MACRO_AS_STRING(Actor, Parent, sqlite_actor_basic, "parent")
SETGET_MACRO_AS_STRING(Actor, CreatedOn, sqlite_actor_basic, "createdon")
SETGET_MACRO_AS_APRDATE(Actor, CreatedOn, sqlite_actor_basic, "createdon")
SETGET_MACRO_AS_STRING(Actor, ValidFrom, sqlite_actor_basic, "validfrom")
SETGET_MACRO_AS_APRDATE(Actor, ValidFrom, sqlite_actor_basic, "validfrom")
SETGET_MACRO_AS_STRING(Actor, ExpiresOn, sqlite_actor_basic, "expireson")
SETGET_MACRO_AS_APRDATE(Actor, ExpiresOn, sqlite_actor_basic, "expireson")
SETGET_MACRO_AS_STRING(Actor, LastAccess, sqlite_actor_basic, "lastaccess")
SETGET_MACRO_AS_APRDATE(Actor, LastAccess, sqlite_actor_basic, "lastaccess")
ADDRMV_MACRO_AS_CHARFLAGS(Actor, ActorMark, sqlite_actor_basic, "actormark")
SETGET_MACRO_AS_STRING(Actor, ExternalAlias, sqlite_actor_basic, "externalalias")
SETGET_MACRO_AS_INT64(Actor, BlocksDone, sqlite_actor_basic, "blocksdone")
SETGET_MACRO_AS_INT64(Actor, BlocksTotal, sqlite_actor_basic, "blockstotal")
INCDEC_MACRO_AS_INT64(Actor, BlocksDone, sqlite_actor_basic, "blocksdone")
INCDEC_MACRO_AS_INT64(Actor, BlocksTotal, sqlite_actor_basic, "blockstotal")

SETGET_MACRO_AS_INT64(Actor, UserCredit, sqlite_actor_extended, "credit")
SETGET_MACRO_AS_INT64(Actor, UserQuota, sqlite_actor_extended, "quota")

SETGET_MACRO_AS_STRING(Actor, ComputeHost, sqlite_actor_extended, "host")
SETGET_MACRO_AS_STRING(Actor, Capabilities, sqlite_actor_extended, "capabilities")

SETGET_MACRO_AS_INT(Actor, WorkInstanceLocalID, sqlite_actor_extended, "instanceid")
SETGET_MACRO_AS_STRING(Actor, WorkInstanceAccessCluster, sqlite_actor_extended, "assignedcluster")
SETGET_MACRO_AS_STRING(Actor, WorkInstancePreferredCluster, sqlite_actor_extended, "preferredclusters")
SETGET_MACRO_AS_STRING(Actor, WorkInstanceDataStore, sqlite_actor_extended, "inputdatastore")
SETGET_MACRO_AS_STRING(Actor, WorkInstanceCompute, sqlite_actor_extended, "outputcompute")
SETGET_MACRO_AS_INT(Actor, WorkInstanceResultBlocks, sqlite_actor_extended, "resultblocks")

SETGET_MACRO_AS_INT(Actor, DataStoreBlocksHeld, sqlite_actor_extended, "blocksheld")
SETGET_MACRO_AS_INT64(Actor, DataStoreBlockSize, sqlite_actor_extended, "blocksize")
SETGET_MACRO_AS_STRING(Actor, DataStoreWorkHash, sqlite_actor_extended, "datahash")
SETGET_MACRO_AS_INT(Actor, DataStoreLocalIDNos, sqlite_actor_extended, "instances")
SETGET_MACRO_AS_STRING(Actor, DataStoreOrigin, sqlite_actor_extended, "origin")
SETGET_MACRO_AS_STRING(Actor, DataStoreDecryptionKey, sqlite_actor_extended, "decryptionkey")

/** Pre-cache all actor data. Name must be specified first. */
void Actor::precacheAll()
{
   FATAL_IF_BLANK((void)0, getName(), "actor name");
   if (sqlite_actor_basic) sqlite_actor_basic->precache();
   getType();
   if (sqlite_actor_extended) sqlite_actor_extended->precache();
}

/** Commit data that has been updated or set.
 * 
 * @return True on success, false if the name or type is unset. */
bool Actor::commit()
{
   // Can't update this and throw an error for things that don't exist
   // can't return false
   if (sqlite_actor_basic != NULL) sqlite_actor_basic->commit();
   if (sqlite_actor_extended != NULL) sqlite_actor_extended->commit();
   return true;
}

/** Commit data that has been updated.
 * 
 * @return True on success, false if the name or type is unset. */
bool Actor::update()
{
   // Can't update this and throw an error for things that don't exist
   // can't return false
   if (sqlite_actor_basic != NULL) sqlite_actor_basic->update();
   if (sqlite_actor_extended != NULL) sqlite_actor_extended->update();
   return true;
}

/** Set defaults, but no allocations. */
void Actor::initNull()
{
   newadb = NULL;
   thistype = ACTOR_TYPE_UNSET;
}

/** Update the actor blocks complete and to do, based on the incoming
 * message. This can increment, decrement and set all of the current
 * block counts as well as the blocks to do. This gives complete control
 * over the progress indicators to the remote application.
 * 
 * @param msgin
 * The incoming broker message containing instructions on how to change
 * the block counts.
 * 
 * @return
 * Zero on no changes, one if changes were applied successfully. */
int Actor::updateBlocks(BrokerMessage &msgin)
{
   int updated = 0;
   const APRTable *headertable = msgin.getHeaderTable();
   if (headertable->get("Update-Total-Blocks"))
   {
      setBlocksTotal(atoi(headertable->get("Update-Total-Blocks")));
      updated = 1;
   }
   if (headertable->get("Update-Done-Blocks"))
   {
      setBlocksDone(atoi(headertable->get("Update-Done-Blocks")));
      updated = 1;
   }
   if (headertable->get("Add-Total-Blocks"))
   {
      addToBlocksTotal(atoi(headertable->get("Add-Total-Blocks")));
      updated = 1;
   }
   if (headertable->get("Add-Done-Blocks"))
   {
      addToBlocksDone(atoi(headertable->get("Add-Done-Blocks")));
      updated = 1;
   }
   if (headertable->get("Subtract-Total-Blocks"))
   {
      subtractFromBlocksTotal(atoi(headertable->get("Subtract-Total-Blocks")));
      updated = 1;
   }
   if (headertable->get("Subtract-Done-Blocks"))
   {
      subtractFromBlocksDone(atoi(headertable->get("Subtract-Done-Blocks")));
      updated = 1;
   }
   return updated;
}

/** This will generate a table of all of the metadata associated
 * with the actor described by this class.
 * 
 * @param mp
 * The memory pool to use to create the table.
 * 
 * @return
 * The APRTable class pointer on success, NULL on failure. */
APRTable *Actor::getMetadata(apr_pool_t *mp)
{
   APRTable *ttable = new APRTable(mp);
   int countidx = 0;
   while (ActorsDB::table_actors_fields_names[countidx] != NULL)
   {
      const char *this_field_name = ActorsDB::table_actors_fields_names[countidx];
      const char *gotfield = (sqlite_actor_basic->getField(this_field_name));
      char *field_name = StringUtils::varprintf("Actor-%s", this_field_name);
      ttable->set(field_name, gotfield);
      free(field_name);
      countidx++;
   }
   // prime type data
   const Actor::actortype this_actor_type = getType();
   const char **extended_information = NULL;
   const char *metadata_packet_descriptor = NULL;
   switch (this_actor_type)
   {
      case (Actor::ACTOR_TYPE_USER):
      {
         extended_information = ActorsDB::table_users_fields_names;
         metadata_packet_descriptor = "User";
         break;
      }
      case (Actor::ACTOR_TYPE_COMPUTE):
      {
         extended_information = ActorsDB::table_computes_fields_names;
         metadata_packet_descriptor = "Compute";
         break;
      }
      case (Actor::ACTOR_TYPE_WORKINSTANCE):
      {
         extended_information = ActorsDB::table_workinstances_fields_names;
         metadata_packet_descriptor = "WorkInstance";
         break;
      }
      case (Actor::ACTOR_TYPE_DATASTORE):
      {
         extended_information = ActorsDB::table_datastores_fields_names;
         metadata_packet_descriptor = "DataStore";
         break;
      }
      default: break;
   }
   if (extended_information == NULL)
   {
      errprint("could not find a type for actor '%s' to extract extended information from", getName());
      delete ttable;
      return NULL;
   }
   countidx = 0;
   while (extended_information[countidx] != NULL)
   {
      const char *this_field_name = extended_information[countidx];
      const char *gotfield = (sqlite_actor_extended->getField(this_field_name));
      char *field_name = StringUtils::varprintf("%s-%s", metadata_packet_descriptor, this_field_name);
      ttable->set(field_name, gotfield);
      free(field_name);
      countidx++;
   }
   return ttable;
}

/** Set the actor to not error if not committed. */
void Actor::setUncommitable()
{
   if (sqlite_actor_basic)
      sqlite_actor_basic->doNotSave();
   if (sqlite_actor_extended)
      sqlite_actor_extended->doNotSave();
}
